//
// Created by root on 4/29/17.
//

#include "../runner.h"

WRAP_SCHEME_OP2(real_add)
WRAP_SCHEME_OP2(real_mul)
WRAP_SCHEME_OP2(real_sub)
WRAP_SCHEME_OP2(real_div)
WRAP_SCHEME_OP2(complex_add)
WRAP_SCHEME_OP2(complex_mul)
WRAP_SCHEME_OP2(complex_sub)
WRAP_SCHEME_OP2(complex_div)

#define OP_NUMBER_INTEGER(_op,_x,_y,_z) Cell* arg_x = make_integer(global_scheme, _x); \
    Cell* arg_y = make_integer(global_scheme, _y); \
    Cell* ret = make_integer(global_scheme, _z); \
    expect_value(__wrap_real_##_op, sc, global_scheme); \
    expect_value(__wrap_real_##_op, x, arg_x); \
    expect_value(__wrap_real_##_op, y, arg_y); \
    will_return(__wrap_real_##_op, ret); \
    Cell* r = number_##_op \
    (global_scheme, arg_x, arg_y)

#define OP_NUMBER_FRACTION(_op,_a,_b,_c,_d,_e,_f) Cell* arg_x = \
    make_fraction(global_scheme, make_integer(global_scheme, _a), make_integer(global_scheme, _b)); \
    Cell* arg_y = make_fraction(global_scheme, make_integer(global_scheme, _c), make_integer(global_scheme, _d)); \
    Cell* ret = make_fraction(global_scheme, make_integer(global_scheme, _e), make_integer(global_scheme, _f)); \
    expect_value(__wrap_real_##_op, sc, global_scheme); \
    expect_value(__wrap_real_##_op, x, arg_x); \
    expect_value(__wrap_real_##_op, y, arg_y); \
    will_return(__wrap_real_##_op, ret); \
    Cell* r = number_##_op \
    (global_scheme, arg_x, arg_y)

#define OP_NUMBER_DECIMAL(_op,_x,_y,_z) Cell* arg_x = make_decimal(global_scheme, _x); \
    Cell* arg_y = make_decimal(global_scheme, _y); \
    Cell* ret = make_decimal(global_scheme, _z); \
    expect_value(__wrap_real_##_op, sc, global_scheme); \
    expect_value(__wrap_real_##_op, x, arg_x); \
    expect_value(__wrap_real_##_op, y, arg_y); \
    will_return(__wrap_real_##_op, ret); \
    Cell* r = number_##_op \
    (global_scheme, arg_x, arg_y)

#define TEST_OK  assert_true(eqn(r, ret))

/**
 * normal cases for number operation
 * */

//number_add
static void test_number_add_when_type_is_integer() {
    OP_NUMBER_INTEGER(add, 1, 2, 3);
    TEST_OK;
}

static void test_number_add_when_type_is_fraction() {
    OP_NUMBER_FRACTION(add, 1, 2, 3, 4, 5, 4);
    TEST_OK;
}

static void test_number_add_when_type_is_decimal() {
    OP_NUMBER_DECIMAL(add, 0.1, 0.2, 0.3);
    TEST_OK;
}

static void test_number_add_when_type_is_complex() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* ret = make_complex(global_scheme, make_integer(global_scheme, 4), make_integer(global_scheme, 6));
    expect_value(__wrap_complex_add, sc, global_scheme);
    expect_value(__wrap_complex_add, x, arg_x);
    expect_value(__wrap_complex_add, y, arg_y);
    will_return(__wrap_complex_add, ret);
    Cell* r = complex_add(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

//number_mul
static void test_number_mul_when_type_is_integer() {
    OP_NUMBER_INTEGER(mul, 2, 3, 6);
    TEST_OK;
}

static void test_number_mul_when_type_is_fraction() {
    OP_NUMBER_FRACTION(mul, 1, 2, 3, 4, 3, 8);
    TEST_OK;
}

static void test_number_mul_when_type_is_decimal() {
    OP_NUMBER_DECIMAL(mul, 0.1, 0.2, 0.02);
    TEST_OK;
}

static void test_number_mul_when_type_is_complex() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* ret = make_complex(global_scheme, make_integer(global_scheme, -5), make_integer(global_scheme, 10));
    expect_value(__wrap_complex_mul, sc, global_scheme);
    expect_value(__wrap_complex_mul, x, arg_x);
    expect_value(__wrap_complex_mul, y, arg_y);
    will_return(__wrap_complex_mul, ret);
    Cell* r = complex_mul(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

//number_sub
static void test_number_sub_when_type_is_integer() {
    OP_NUMBER_INTEGER(sub, 1, 2, -1);
    TEST_OK;
}

static void test_number_sub_when_type_is_fraction() {
    OP_NUMBER_FRACTION(sub, 1, 2, 3, 4, -1, 4);
    TEST_OK;
}

static void test_number_sub_when_type_is_decimal() {
    OP_NUMBER_DECIMAL(sub, 0.1, 0.2, -0.1);
    TEST_OK;
}

static void test_number_sub_when_type_is_complex() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* ret = make_complex(global_scheme, make_integer(global_scheme, -2), make_integer(global_scheme, -2));
    expect_value(__wrap_complex_sub, sc, global_scheme);
    expect_value(__wrap_complex_sub, x, arg_x);
    expect_value(__wrap_complex_sub, y, arg_y);
    will_return(__wrap_complex_sub, ret);
    Cell* r = complex_sub(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

//number_div
static void test_number_div_when_type_is_integer() {
    OP_NUMBER_INTEGER(div, 4, 2, 2);
    TEST_OK;
}

static void test_number_div_when_type_is_fraction() {
    OP_NUMBER_FRACTION(div, 1, 2, 3, 4, 2, 3);
    TEST_OK;
}

static void test_number_div_when_type_is_decimal() {
    OP_NUMBER_DECIMAL(sub, 0.1, 0.2, 0.5);
    TEST_OK;
}

static void test_number_div_when_type_is_complex() {
    Cell* arg_x = make_complex(global_scheme, make_integer(global_scheme, 1), make_integer(global_scheme, 2));
    Cell* arg_y = make_complex(global_scheme, make_integer(global_scheme, 3), make_integer(global_scheme, 4));
    Cell* a = make_fraction(global_scheme, make_integer(global_scheme, 11), make_integer(global_scheme, 25));
    Cell* b = make_fraction(global_scheme, make_integer(global_scheme, 2), make_integer(global_scheme, 25));
    Cell* ret = make_complex(global_scheme, a, b);
    expect_value(__wrap_complex_div, sc, global_scheme);
    expect_value(__wrap_complex_div, x, arg_x);
    expect_value(__wrap_complex_div, y, arg_y);
    will_return(__wrap_complex_div, ret);
    Cell* r = complex_div(global_scheme, arg_x, arg_y);
    assert_true(eqn(r, ret));
}

/**
 * cases for number operation when result type changed
 * */
//no need to test

/**
 * cases for number operation when arguments are not numbers
 * */


#define OP_NUMBER_FIRST_ARG_INVALID(_op) Cell* arg_x = make_string(global_scheme, "A_STRING"); \
    Cell* arg_y = make_integer(global_scheme, 2); \
    Cell* r = number_##_op \
    (global_scheme, arg_x, arg_y)

#define OP_NUMBER_SECOND_ARG_INVALID(_op) Cell* arg_x = make_integer(global_scheme, 1); \
    Cell* arg_y = make_string(global_scheme, "A_STRING"); \
    Cell* r = number_##_op \
    (global_scheme, arg_x, arg_y)


//number_add
static void test_number_add_when_first_arg_is_not_number() {
    OP_NUMBER_FIRST_ARG_INVALID(add);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

static void test_number_add_when_second_arg_is_not_number() {
    OP_NUMBER_SECOND_ARG_INVALID(add);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

//number_mul
static void test_number_mul_when_first_arg_is_not_number() {
    OP_NUMBER_FIRST_ARG_INVALID(mul);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

static void test_number_mul_when_second_arg_is_not_number() {
    OP_NUMBER_SECOND_ARG_INVALID(mul);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

//number_sub
static void test_number_sub_when_first_arg_is_not_number() {
    OP_NUMBER_FIRST_ARG_INVALID(sub);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

static void test_number_sub_when_second_arg_is_not_number() {
    OP_NUMBER_SECOND_ARG_INVALID(sub);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

//number_div
static void test_number_div_when_first_arg_is_not_number() {
    OP_NUMBER_FIRST_ARG_INVALID(div);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

static void test_number_div_when_second_arg_is_not_number() {
    OP_NUMBER_SECOND_ARG_INVALID(div);
    TEST_INVALID_TYPE_EXCEPTION(r);
}

/**
 * cases for number div when divided by zero
 * */

//number_div
static void test_number_div_when_divided_by_int_zero() {
    Cell* arg_x = make_integer(global_scheme, 1);
    Cell* arg_y = make_integer(global_scheme, 0);
    Cell* r = number_div(global_scheme, arg_x, arg_y);
    TEST_DIVISION_BY_ZERO(r);
}

static void test_number_div_when_divided_by_fraction_zero() {
    Cell* arg_x = make_integer(global_scheme, 1);
    Cell* arg_y = make_fraction(global_scheme, make_integer(global_scheme, 0), make_integer(global_scheme, 1));
    Cell* r = number_div(global_scheme, arg_x, arg_y);
    TEST_DIVISION_BY_ZERO(r);
}

static void test_number_div_when_divided_by_decimal_zero() {
    Cell* arg_x = make_integer(global_scheme, 1);
    Cell* arg_y = make_decimal(global_scheme, 0.0);
    Cell* r = number_div(global_scheme, arg_x, arg_y);
    TEST_DIVISION_BY_ZERO(r);
}

//TODO
int main(int argc, char **argv) {
    const struct CMUnitTest tests[] = {
            /*number_add*/
            cmocka_unit_test(test_number_add_when_type_is_integer),
            cmocka_unit_test(test_number_add_when_type_is_fraction),
            cmocka_unit_test(test_number_add_when_type_is_decimal),
            cmocka_unit_test(test_number_add_when_type_is_complex),
            cmocka_unit_test(test_number_add_when_first_arg_is_not_number),
            cmocka_unit_test(test_number_add_when_second_arg_is_not_number),
            /*number_mul*/
            cmocka_unit_test(test_number_mul_when_type_is_integer),
            cmocka_unit_test(test_number_mul_when_type_is_fraction),
            cmocka_unit_test(test_number_mul_when_type_is_decimal),
            cmocka_unit_test(test_number_mul_when_type_is_complex),
            cmocka_unit_test(test_number_mul_when_first_arg_is_not_number),
            cmocka_unit_test(test_number_mul_when_second_arg_is_not_number),
            /*number_sub*/
            cmocka_unit_test(test_number_sub_when_type_is_integer),
            cmocka_unit_test(test_number_sub_when_type_is_fraction),
            cmocka_unit_test(test_number_sub_when_type_is_decimal),
            cmocka_unit_test(test_number_sub_when_type_is_complex),
            cmocka_unit_test(test_number_sub_when_first_arg_is_not_number),
            cmocka_unit_test(test_number_sub_when_second_arg_is_not_number),
            /*number_div*/
            cmocka_unit_test(test_number_div_when_type_is_integer),
            cmocka_unit_test(test_number_div_when_type_is_fraction),
            cmocka_unit_test(test_number_div_when_type_is_decimal),
            cmocka_unit_test(test_number_div_when_type_is_complex),
            cmocka_unit_test(test_number_div_when_first_arg_is_not_number),
            cmocka_unit_test(test_number_div_when_second_arg_is_not_number),
            cmocka_unit_test(test_number_div_when_divided_by_int_zero),
            cmocka_unit_test(test_number_div_when_divided_by_fraction_zero),
            cmocka_unit_test(test_number_div_when_divided_by_decimal_zero),
    };
    return cmocka_run_group_tests(tests, setup_scheme, teardown_scheme);
}
